using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data.OleDb;
using System.Globalization;
using System.IO;
using System.Text;
using Wax;
using Wax.Provider;

namespace Wax.Runner
{
    /// <summary>
    /// Executes a series of tests defined by the <see cref="FileName"/> property.
    /// </summary>
    public class TestRunner
    {
        #region Private fields

        /// <summary>
        /// Name including full path to the file containing the test scripts to execute.
        /// </summary>
        private string fileName;

        /// <summary>
        /// The fileInfo for the current <see cref="FileName"/>
        /// </summary>
        private FileInfo fileInfo;

        /// <summary>
        /// A collection of any errors that occured during <see cref="Execute">execution</see>.
        /// </summary>
        private Collection<TestError> errors;

        /// <summary>
        /// List of allowed file extensions.
        /// </summary>
        private readonly Collection<string> allowedFileExtensions;

        /// <summary>
        /// Tests initalized by <see cref="Load"/>.
        /// </summary>
        private Collection<Test> tests;

        /// <summary>
        /// <c>true</c> if any errors occured whilst executing the tests; otherwise <c>false</c>.
        /// </summary>
        private bool errorsOccured;

        #endregion

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="TestRunner"/> class.
        /// </summary>
        public TestRunner()
        {
            this.allowedFileExtensions = new Collection<string>(new string[] { ".xls", ".xlsx" });            
            this.tests = new Collection<Test>();
            this.errors = new Collection<TestError>();
        }
        
        /// <summary>
        /// Initializes a new instance of the <see cref="TestRunner"/> class.
        /// </summary>
        /// <param name="fileName">Name of the file containing the test script.</param>
        public TestRunner(string fileName) : this()
        {
            this.FileName = fileName;
        }

        #endregion

        #region Public events

        /// <summary>
        /// Occurs when the load operation has completed.
        /// </summary>
        public event EventHandler LoadComplete;

        /// <summary>
        /// Occurs when execution completed.
        /// </summary>
        public event EventHandler ExecutionCompleted;

        /// <summary>
        /// Occurs when execution started.
        /// </summary>
        public event EventHandler ExecutionStarted;

        /// <summary>
        /// Occurs when the test execution has started.
        /// </summary>
        public event EventHandler TestStarted;

        /// <summary>
        /// Occurs when execution has completed.
        /// </summary>
        public event EventHandler TestCompleted;

        /// <summary>
        /// Occurs when a test encounters an error executing a command.
        /// </summary>
        public event EventHandler<TestErrorEventArgs> TestErrorOccured;

        /// <summary>
        /// Occurs when progress is made, each time a command has been successfully executed.
        /// </summary>
        public event EventHandler<TestProgressEventArgs> Progress;

        /// <summary>
        /// Occurs when a command completes.
        /// </summary>
        public event EventHandler<CommandCompletedEventArgs> CommandCompleted;

        #endregion


        #region Public properties

        /// <summary>
        /// Name including full path to the file containing the test scripts to execute.
        /// </summary>
        /// <exception cref="FileNotFoundException">If assigned a file that does not exist.</exception>
        /// <exception cref="ArgumentException">If the assigned file is not of a supported type, see: <see cref="AllowedFileExtensions"/></exception>
        public string FileName
        {
            get { return fileName; }
            set
            {
                this.ValidateFileName(value);
                fileName = value;
            }
        }

        /// <summary>
        /// List of allowed file extensions.
        /// </summary>
        public Collection<string> AllowedFileExtensions
        {
            get { return allowedFileExtensions; }
        }

        /// <summary>
        /// Tests initalized by <see cref="Load"/>.
        /// </summary>
        public Collection<Test> Tests
        {
            get { return tests; }
        }

        /// <summary>
        /// <c>true</c> if any errors occured whilst executing the tests; otherwise <c>false</c>.
        /// </summary>
        public bool ErrorsOccured
        {
            get { return errorsOccured; }
        }

        /// <summary>
        /// A collection of any errors that occured during <see cref="Execute">execution</see>.
        /// </summary>
        public Collection<TestError> Errors
        {
            get { return errors; }
        }

        #endregion

        #region Public instance methods

        /// <summary>
        /// Initializes the <see cref="TestManager"/> from the specified <see cref="FileName"/>.
        /// </summary>
        public void Load()
        {
            this.tests = WaxDataManager.Provider.ParseFile(this.FileName);

            if (this.LoadComplete != null)
            {
                this.LoadComplete(this, EventArgs.Empty);
            }
        }

        /// <summary>
        /// Executes this instance.
        /// </summary>
        public void Execute()
        {
            using (TestManager testManager = new TestManager())
            {
                PreExecuteInitialize(testManager);

                if (this.ExecutionStarted != null)
                {
                    this.ExecutionStarted(this, EventArgs.Empty);
                }

                testManager.Execute();

                if (this.ExecutionCompleted != null)
                {
                    this.ExecutionCompleted(this, EventArgs.Empty);
                }
            }
        }

        #endregion

        #region Private instance methods

        /// <summary>
        /// Performs any initialization required before test execution.
        /// </summary>
        /// <param name="testManager">The test manager.</param>
        private void PreExecuteInitialize(TestManager testManager)
        {
            this.errorsOccured = false;
            this.errors = new Collection<TestError>();
            testManager.ThrowErrors = false;
            testManager.TestStarted += new EventHandler(TestManagerTestStarted);
            testManager.TestCompleted += new EventHandler(TestManagerTestCompleted);
            testManager.TestErrorOccurred += new EventHandler<TestErrorEventArgs>(TestManagerTestErrorOccured);
            testManager.Progress += new EventHandler<TestProgressEventArgs>(TestManagerProgress);
            testManager.CommandCompleted += new EventHandler<CommandCompletedEventArgs>(TestManagerCommandCompleted);
            foreach (Test test in this.Tests)
            {
                testManager.Tests.Add(test);
            }
        }

        /// <summary>
        /// Handles the Progress event of the testManager control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="Wax.TestProgressEventArgs"/> instance containing the event data.</param>
        private void TestManagerProgress(object sender, TestProgressEventArgs e)
        {
            if (this.Progress != null)
            {
                this.Progress(sender, e);
            }
        } 

        /// <summary>
        /// Handles the <see cref="TestManager.TestCompleted"/> event.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void TestManagerTestCompleted(object sender, EventArgs e)
        {
            if (this.TestCompleted != null)
            {
                this.TestCompleted(sender, e);
            }
        }

        /// <summary>
        /// Handles the CommandCompleted event of the testManager control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="Wax.CommandCompletedEventArgs"/> instance containing the event data.</param>
        void TestManagerCommandCompleted(object sender, CommandCompletedEventArgs e)
        {
            if (this.CommandCompleted != null)
            {
                this.CommandCompleted(sender, e);
            }
        }

        /// <summary>
        /// Handles the <see cref="TestManager.TestStarted"/> event..
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void TestManagerTestStarted(object sender, EventArgs e)
        {
            if (this.TestStarted != null)
            {
                this.TestStarted(sender, e);
            }
        }

        /// <summary>
        /// Handles the TestErrorOccurred event of the testManager control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="Wax.TestErrorEventArgs"/> instance containing the event data.</param>
        private void TestManagerTestErrorOccured(object sender, TestErrorEventArgs e)
        {
            this.errorsOccured = true;
            this.errors.Add(e.Error);

            if (this.TestErrorOccured != null)
            {
                this.TestErrorOccured(sender, e);
            }
        }       

        /// <summary>
        /// Validates the file.
        /// </summary>
        /// <param name="value">Name of the file.</param>
        private void ValidateFileName(string value)
        {
            if (!File.Exists(value))
            {
                throw new FileNotFoundException(string.Format("Could not find file {0}", value), value);
            }

            this.fileInfo = new FileInfo(value);

            bool foundValidExtension = false;
            foreach (string extension in this.AllowedFileExtensions)
            {
                if (fileInfo.Extension.Equals(extension, StringComparison.InvariantCultureIgnoreCase))
                {
                    foundValidExtension = true;
                }
            }

            if (!foundValidExtension)
            {
                throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "File is not of supported type. {0} can not be read", fileInfo.Extension), "FileName");
            }
        }

        #endregion
    }
}
